﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using Microsoft.CSharp;
using System.Collections.ObjectModel;

namespace Simhopp.Common
{
    /// <summary>
    /// The Diver class provides a description of a diver.
    /// </summary>
    [Serializable]
    public class Diver : INotifyPropertyChanged, IComparable, IDatabaseObject, ISerializable
    {
        #region FIELD REGION
        private int id;
        private Credentials credentials = new Credentials();
        private List<Dive> dives;
        private const int NumberOfRounds = 3;
        private int credentialsId; // Used for the database
        private int competitionId; // Used for the database

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
        #region CONSTRUCTOR REGION
        /// <summary>
        /// Default constructor.
        /// </summary>
        public Diver()
        {
            id = -1;
            dives = new List<Dive>(new Dive[3] { new Dive(), new Dive(), new Dive() });
            credentials.Name = "";
            credentials.Gender = Genders.Male;
            credentials.Age = 0;
            credentials.Nationality = "";
            credentials.SocialSecurityNumber = "";
        }

        #endregion
        #region PROPERTY REGION

        /// <summary>
        /// Property Id gets or sets id.
        /// </summary>
        public int Id
        {
            get { return id; }
            set { 
                id = value;
                foreach (var dive in dives)
                    dive.DiverId = id;
            }
        }

        /// <summary>
        /// Property CompetitionId gets or sets the competitionId field.
        /// </summary>
        public int CompetitionId
        {
            get { return competitionId; }
            set { competitionId = value; }
        }

        /// <summary>
        /// Property Name gets or sets the name field.
        /// </summary>
        public String Name
        {
            get { return credentials.Name; }
            set { credentials.Name = value;
            NotifyPropertyChanged();
            }
        }

        /// <summary>
        /// Proprety TotalScore gets or sets the totalscore field.
        /// </summary>
        public double TotalScore
        {
            get
            {
                double scoreSum = 0;
                foreach (var item in this.Dives)
                {
                    scoreSum += item.TotalScore;
                };
                return scoreSum;
            }
        }

        /// <summary>
        /// Property Gender gets or sets the gender field.
        /// </summary>
        public Genders Gender
        {
            get { return credentials.Gender; }
            set
            {
                credentials.Gender = value;
                NotifyPropertyChanged();
            }
        }

        /// <summary>
        /// Property Age gets or sets the age field.
        /// </summary>
        public int Age
        {
            get { return credentials.Age; }
            set
            {
                credentials.Age = value;
                NotifyPropertyChanged();
            }
        }

        /// <summary>
        /// Property Nationality gets or sets the nationality field.
        /// </summary>
        public String Nationality
        {
            get { return credentials.Nationality; }
            set
            {
                credentials.Nationality = value;
                NotifyPropertyChanged();
            }
        }

        /// <summary>
        /// Property SocialSecurityNumber gets or sets the social security number.
        /// </summary>
        public String SocialSecurityNumber
        {
            get { return credentials.SocialSecurityNumber; }
            set
            {
                credentials.SocialSecurityNumber = value;
                NotifyPropertyChanged();
            }
        }

        /// <summary>
        /// Property DiveCount gets the number of dives currently associated with the diver.
        /// </summary>
        public int DiveCount
        {
            get { return dives.Count; }
        }

        /// <summary>
        /// Property Dives returns the dive list as read only.
        /// </summary>
        public IList<Dive> Dives
        {
            get { return dives; }
        }

        /// <summary>
        /// Property CredentialsId gets or sets the credentialsId.
        /// </summary>
        public Credentials Credentials
        {
            get { return credentials; }
            set { credentials = value; }
        }

        /// <summary>
        /// Property CredentialsId gets or sets the credentialsId.
        /// </summary>
        public int CredentialsId
        {
            get { return credentialsId; }
            set { credentialsId = value; }
        }

        #endregion
        #region PRIVATE HELPER METHOD REGION

        private bool IsInDiveList(Dive aDive)
        {
            bool found = false;
            foreach (var dive in dives)
            {
                if (dive == aDive)
                {
                    found = true;
                }
            }
            return found;
        }
        // Private helper method used to check whether the specified dive is in the dive list.

        /// <summary>
        /// Notifies to the subscribers that a property has changed.
        /// </summary>
        /// <param name="propertyName"></param>
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
        #region METHOD REGION

        /// <summary>
        /// Checks if two instances of the object are identical
        /// </summary>
        /// <param name="obj">The object to compare with</param>
        /// <returns>True/False</returns>
        public override bool Equals(object obj)
        {

            return ((obj as Diver) == null) ? false : (obj as Diver).Id == this.Id;
        }

        /// <summary>
        /// Used in addition to Equals method
        /// </summary>
        /// <returns>A unique id</returns>
        public override int GetHashCode()
        {
            return id;
        }

        /// <summary>
        /// Adds a specified dive to the dive list.
        /// </summary>
        /// <param name="dive">The dive to add.</param>
        public void AddDive(Dive dive)
        {
            if (this.dives.Count == NumberOfRounds)
            {
                throw new DiveListFullException("The dive list is full.");
            }
            dives.Add(dive);
        }

        /// <summary>
        /// Adds a score to the specified dive.
        /// </summary>
        /// <param name="roundIndex">The index of the dive in the Dive list.</param>
        /// <param name="score">The score to add.</param>
        public void AddScoreToDive(int roundIndex, Score score)
        {
            dives[roundIndex].AddScore(score);
        }

        /// <summary>
        /// Gets the number of scores set for the current dive.
        /// </summary>
        /// <param name="roundIndex"></param>
        /// <returns></returns>
        public int NoOfScoresSet(int roundIndex)
        {
            return dives[roundIndex].Scores.Count;
        }

        /// <summary>
        /// Updates the total score for the current dive.
        /// </summary>
        /// <param name="roundIndex">The index of the dive in the dive list.</param>
        public void UpdateTotalScore(int roundIndex)
        {
            dives[roundIndex].UpdateTotalScore();
        }

        /// <summary>
        /// Removes a specified dive from the dive list.
        /// </summary>
        /// <param name="dive">The dive to remove.</param>
        public void RemoveDive(Dive dive)
        {
            if (dives.Count <= 0)
            {
                throw new DiveListEmptyException("The dive list is empty.");
            }
            if (!IsInDiveList(dive))
            {
                throw new ArgumentException("The dive is not in the dive list");
            }
            dives.Remove(dive);
        }

        /// <summary>
        /// Removes all the dives from the dive list.
        /// </summary>
        public void RemoveAllDives()
        {
            dives.Clear();
        }

        /// <summary>
        /// Checks to see whether the diver is configured with one dive per round.
        /// </summary>
        /// <returns>True if the diver is properly configured, else false.</returns>
        public bool IsConfigured()
        {
            return (dives.Count == NumberOfRounds) &&
                !dives.Any(dive => dive.TypeName == "");
        }

        /// <summary>
        /// Checks to see whether the diver has recieved scores from all the judges for the current round.
        /// </summary>
        /// <returns>True if the diver is scored for the current round, else false.</returns>
        public bool CurrentRoundIsScored(Rounds currentRound, int NumberOfJudges)
        {
            return dives[(int)currentRound].ScoreCount == NumberOfJudges;
        }

        /// <summary>
        /// Updates the dive heights to correspond to those specified in the competition.
        /// </summary>
        public void UpdateDives(Collection<Heights> heightList)
        {
            for (int i = 0; i < NumberOfRounds; i++)
            {
                dives[i].Height = heightList[i];
            }
        }

        /// <summary>
        /// Method used in the sorting of divers.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int CompareTo(object obj)
        {
            if (obj == null)
                return 1;

            Diver otherDiver = obj as Diver;
            if (otherDiver != null)
            {
                if (this.TotalScore < otherDiver.TotalScore)
                {
                    return 1;
                }
                else
                {
                    return -1;
                }
            }
            else
            {
                throw new ArgumentException("Object is not a Diver.");
            }
        }

        #endregion
        #region DATABASE REGION

        /// <summary>
        /// Creates a dictionary from the object where member field names are mapped to their corresponding values
        /// </summary>
        /// <returns>A dictionary</returns>
        public Dictionary<String, Object> GetDictionary()
        {
            try
            {
                var dictionary = new Dictionary<String, Object>
                {
                    {"Id", Id.ToString(CultureInfo.InvariantCulture)},
                    {"CredentialsId", CredentialsId.ToString(CultureInfo.InvariantCulture)},
                    {"CompetitionId", CompetitionId.ToString(CultureInfo.InvariantCulture)}
                };
                return dictionary;
            }
            catch (Exception e)
            {
                MyLogger.Error("Failed to create dictionary from Diver object - " + e.Message);
                return null;
            }
        }

        /// <summary>
        /// Creates an Diver object from a passed dictionary
        /// </summary>
        /// <param name="data">A dictionary where database field names are mapped to their corresponding values</param>
        /// <returns>A Diver object</returns>
        public static Diver GetFromDictionary(Dictionary<String, Object> data)
        {
            try
            {
                return new Diver
                {
                    Id = (int)data["Id"],
                    CompetitionId = (int)data["CompetitionId"],
                    CredentialsId = (int)data["CredentialsId"],
                    Credentials = null, // CredentialsMap.ReadCredentialsFromDataBase((int)data["CredentialsId"]),
                    dives = new List<Dive>() // DiveMap.ReadAllDivesForOneDiver((int)data["Id"])
                };
            }
            catch (Exception e)
            {
                MyLogger.Error("Failed to create Diver object from dictionary - " + e.Message);
                return null;
            }
        }

        /// <summary>
        /// Returns the name of the Id field used in the corresponding database-table
        /// </summary>
        /// <returns></returns>
        public String GetIdFieldName()
        {
            return "Id";
        }

        /// <summary>
        /// Returns the name of the Table used in the database
        /// </summary>
        /// <returns></returns>
        public String GetTableName()
        {
            return "Divers";
        }

        #endregion
        #region SERIALIZE/DESERIALIZE
        /// <summary>
        /// Adds the class specific information used in the serialization of an object.
        /// </summary>
        /// <param name="info">The class specific information.</param>
        /// <param name="context">Describes the source and destination of a given serialized stream.</param>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("id", id, typeof(int));
            info.AddValue("credentials", credentials, typeof(Credentials));
            info.AddValue("dives", dives, typeof(List<Dive>));
            info.AddValue("credentialsId", credentialsId, typeof(int));
            info.AddValue("competitionId", competitionId, typeof(int));
        }
        /// <summary>
        /// Constructor used in the deserialization of an object.
        /// </summary>
        /// <param name="info">The class specific information.</param>
        /// <param name="context">Describes the source and destination of a given serialized stream.</param>
        protected Diver(SerializationInfo info, StreamingContext context)
        {
            id = (int) info.GetValue("id", typeof(int));
            credentials = (Credentials) info.GetValue("credentials",  typeof(Credentials));
            dives = (List<Dive>) info.GetValue("dives",  typeof(List<Dive>));
            credentialsId = (int) info.GetValue("credentialsId", typeof(int));
            competitionId = (int)info.GetValue("competitionId", typeof(int));
        }

        #endregion
    }
}
